﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CardTypes = Wormhole.Engine.CardLibrary.CardTypes;
using CardProperties = Wormhole.Engine.CardLibrary.CardProperties;
using BaseCardTypes = Wormhole.Engine.CardLibrary.BaseCardTypes;

namespace Wormhole.Engine.Mission
{
	#region Delegates
	/// <summary>
	/// This delegate is used for events that send notifications regarding card actions.
	/// </summary>
	/// <param name="card">The card the action was performed on.</param>
	public delegate void CardActionDelegate(CardLibrary.BaseCardTypes.Card card);
	#endregion

	/// <author>Rodney McKay</author>
	/// <date>10 April 2008</date>
	/// <summary>
	/// This class represents a mission being played.
	/// </summary>
	public class Mission
	{
		#region Delegates
		/// <summary>
		/// The delegate for the Mission Played event.
		/// </summary>
		public delegate void MissionPlayedDelegate();

		/// <summary>
		/// The delegate for the total changed event.
		/// </summary>
		/// <param name="total">The new total.</param>
		public delegate void TotalChangedDelegate(int total);

		/// <summary>
		/// The delegate for the status changed event.
		/// </summary>
		/// <param name="status">The new mission status.</param>
		public delegate void StatusChangedDelegate(MissionStatus status);

		/// <summary>
		/// The delegate for the mission success delegate.
		/// </summary>
		/// <param name="mission">The mission that was completed successfully.</param>
		public delegate void MissionSuccessDelegate(CardLibrary.CardTypes.Mission mission);

		/// <summary>
		/// The delegate for the mission failed event.
		/// </summary>
		/// <param name="mission">The mission that was failed.</param>
		public delegate void MissionFailedDelegate(CardLibrary.CardTypes.Mission mission);

		/// <summary>
		/// The delegate for the glyph assigned event.
		/// </summary>
		/// <param name="character">The card the glyph was assigned to.</param>
		public delegate void GlyphAssignedDelegate(CardTypes.Character character);

		/// <summary>
		/// The delegate for the event which is invoked when a face up obstacle is played.
		/// </summary>
		/// <param name="obstacle">The obstacle which was played.</param>
		public delegate void FaceUpObstaclePlayedDelegate(CardTypes.Obstacle obstacle);
		#endregion

		#region Events
		/// <summary>
		/// This event fires when a mission is played.
		/// </summary>
		public event MissionPlayedDelegate MissionPlayed;

		/// <summary>
		/// This event fires when the total changes.
		/// </summary>
		public event TotalChangedDelegate TotalChanged;

		/// <summary>
		/// This event fires when the mission difficulty changes.
		/// </summary>
		public event TotalChangedDelegate DifficultyChanged;

		/// <summary>
		/// This event fires when the mission status changes.
		/// </summary>
		public event StatusChangedDelegate StatusChanged;

		/// <summary>
		/// This event fires when the mission is completed successfully.
		/// </summary>
		public event MissionSuccessDelegate MissionSuccess;

		/// <summary>
		/// The event which fires when the mission is failed.
		/// </summary>
		public event MissionFailedDelegate MissionFailed;

		/// <summary>
		/// This event fires when a glyph is assigned to a card.
		/// </summary>
		public event GlyphAssignedDelegate GlyphAssigned;

		/// <summary>
		/// This event fires when a mission is placed on the bottom of the mission pile.
		/// </summary>
		public event CardActionDelegate ReplaceMissionBottom;

		/// <summary>
		/// This event fires when a face up obsacle is played.
		/// </summary>
		public event FaceUpObstaclePlayedDelegate FaceUpObstaclePlayed;
		#endregion

		#region Properties

		#region Game
		private Game _game;
		/// <summary>
		/// The game the mission belongs to.
		/// </summary>
		public Game Game 
		{
			get
			{
				return this._game;
			}

			set
			{
				if (this._game != value)
					this._game = value;
			}
		}
		#endregion

		#region Difficulty
		/// <summary>
		/// The mission difficulty.
		/// </summary>
		public int Difficulty 
		{ 
			get; 
			private set; 
		}
		#endregion

		#region Total
		private int _total;
		/// <summary>
		/// The mission total.
		/// </summary>
		public int Total
		{
			get
			{
				return this._total;
			}

			set
			{
				if (this._total != value)
				{
					this._total = value;
					this.OnTotalChanged(value);
				}
			}
		}
		#endregion

		#region IsSuccess
		/// <summary>
		/// Is the mission success conditions being met.
		/// </summary>
		protected bool IsSuccess
		{
			get
			{
				this.UpdateTotal();
				if (this.CurrentMission != null)
					return this.Difficulty <= this.Total;

				return false;
			}
		}
		#endregion

		#region Status
		private MissionStatus _status;
		/// <summary>
		/// The current mission status.
		/// </summary>
		public MissionStatus Status 
		{
			get
			{
				return this._status;
			}

			set
			{
				if (this._status != value)
				{
					this._status = value;
					this.OnStatusChanged(value);
				}
			}
		}
		#endregion

		#region Current Mission
		/// <summary>
		/// The current mission card.
		/// </summary>
		public CardLibrary.CardTypes.Mission CurrentMission { get; set; }
		#endregion

		#region Assigned Hero Cards
		/// <summary>
		/// The assigned hero cards.
		/// </summary>
		public Decks.AssignedCards AssignedHeroCards { get; set; }
		#endregion

		#region Face Up Obstacles
		/// <summary>
		/// Obstacles that was played face up.
		/// </summary>
		public Decks.AssignedCards FaceUpObstacles { get; set; }
		#endregion

		#region Failed Missions
		/// <summary>
		/// Missions that was failed.
		/// </summary>
		public Decks.Deck FailedMissions { get; set; }
		#endregion

		#endregion

		#region Constructor
		/// <summary>
		/// Create a new instance of the mission class.
		/// </summary>
		/// <param name="game">The game the object will belong to.</param>
		public Mission(Game game)
		{
			this.Game = game;
			this.Total = 0;
			this.AssignedHeroCards = new Decks.AssignedCards();
			this.FailedMissions = new Wormhole.Engine.Decks.Deck();
			this.FaceUpObstacles = new Decks.AssignedCards();
		}
		#endregion

		#region Set Engine Events
		/// <summary>
		/// Wire up the engine events that the object will listen for.
		/// </summary>
		public void SetEngineEvents()
		{
			this.Game.MissionPlayed += new Game.MissionPlayedDelegate(Game_MissionPlayed);
			
			this.Game.Player1.Team.CardAssigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardAssigned);
			this.Game.Player1.Team.CardUnassigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardUnassigned);
			this.Game.Player1.UnassignedSupport.CardAssigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardAssigned);
			this.Game.Player1.UnassignedSupport.CardUnassigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardUnassigned);
			this.Game.Player1.CardsInHand.CardPlayed += new Wormhole.Engine.Decks.HandCards.CardPlayedDelegate(CardsInHand_CardPlayed);

			this.Game.Player2.Team.CardAssigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardAssigned);
			this.Game.Player2.Team.CardUnassigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardUnassigned);
			this.Game.Player2.UnassignedSupport.CardAssigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardAssigned);
			this.Game.Player2.UnassignedSupport.CardUnassigned += new Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard.CardAssignedDelegate(Mission_CardUnassigned);
			this.Game.Player2.CardsInHand.CardPlayed += new Wormhole.Engine.Decks.HandCards.CardPlayedDelegate(CardsInHand_CardPlayed);

			this.AssignedHeroCards.CardAdded += new Wormhole.Engine.Decks.Deck.CardAddedDelegate(AssignedHeroCards_CardAdded);
			this.AssignedHeroCards.CardRemoved += new Wormhole.Engine.Decks.Deck.CardRemovedDelegate(AssignedHeroCards_CardRemoved);
		}
		#endregion

		#region Event Handlers

		#region Mission Played
		/// <summary>
		/// The event handler to catch the mission played event from the game.
		/// </summary>
		/// <param name="mission">The mission card that was played.</param>
		private void Game_MissionPlayed(Wormhole.Engine.CardLibrary.CardTypes.Mission mission)
		{
			this.PlayMission(mission);
		}
		#endregion

		#region Card Assigned
		/// <summary>
		/// Handle the event when a card is assigned to the mission.
		/// </summary>
		/// <param name="card">The card that was assigned to the mission.</param>
		private void Mission_CardAssigned(Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard card)
		{
			this.AssignCard(card);

			// remove from the unassigned support if it is not a team card
			if (!card.Traits.Contains( CardProperties.Trait.Team))
				this.Game.GameTurn.HeroPlayer.UnassignedSupport.Remove(card);
		}
		#endregion

		#region Card Unassigned
		/// <summary>
		/// Handle the event when a card is unassigned.
		/// </summary>
		/// <param name="card">The card that was unassigned.</param>
		private void Mission_CardUnassigned(Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard card)
		{
			this.UnassignCard(card);
		}
		#endregion

		#region Hero Card Assigned
		/// <summary>
		/// The event handler for when a hero card was assigned.
		/// </summary>
		/// <param name="card">The card that was added to the assigned collection</param>
		private void AssignedHeroCards_CardAdded(Wormhole.Engine.CardLibrary.BaseCardTypes.Card card)
		{
			this.UpdateTotal();
		}
		#endregion

		#region Hero Card Unassigned
		/// <summary>
		/// The event handler for when a hero card was unassigned.
		/// </summary>
		/// <param name="card">The card that was removed from the assigned collection.</param>
		private void AssignedHeroCards_CardRemoved(Wormhole.Engine.CardLibrary.BaseCardTypes.Card card)
		{
			this.UpdateTotal();
		}
		#endregion

		#region Card Played
		/// <summary>
		/// Handle the event when a card is played.
		/// </summary>
		/// <param name="card"></param>
		void CardsInHand_CardPlayed(Wormhole.Engine.CardLibrary.BaseCardTypes.Card card)
		{
			CardLibrary.CardTypes.Obstacle obstacle = card as CardLibrary.CardTypes.Obstacle;
			if (obstacle != null)
				this.OnFaceUpObstaclePlayed(obstacle);
		}
		#endregion

		#endregion

		#region Play Mission
		/// <summary>
		/// Play a mission.
		/// </summary>
		/// <param name="mission">The mission card that was played.</param>
		protected void PlayMission(Wormhole.Engine.CardLibrary.CardTypes.Mission mission)
		{
			this.CurrentMission = mission;
			this.Status = MissionStatus.Active;
			this.OnMissionPlayed();
		}
		#endregion

		#region On Mission Played
		/// <summary>
		/// Fire the mission played event.
		/// </summary>
		protected void OnMissionPlayed()
		{
			this.UpdateDifficulty();
			if (this.MissionPlayed != null)
				this.MissionPlayed();
		}
		#endregion

		#region On Total Changed
		/// <summary>
		/// Raise the total changed event.
		/// </summary>
		/// <param name="total">The new total</param>
		protected void OnTotalChanged(int total)
		{
			if (this.TotalChanged != null)
				this.TotalChanged(total);
		}
		#endregion

		#region On Status Changed
		protected void OnStatusChanged(MissionStatus status)
		{
			if (this.StatusChanged != null)
				this.StatusChanged(status);
		}
		#endregion

		#region On Mission Success
		/// <summary>
		/// Fire the mission success event.
		/// </summary>
		/// <param name="mission">The mission that was completed successfully.</param>
		protected void OnMissionSuccess(CardLibrary.CardTypes.Mission mission)
		{
			this.Status = MissionStatus.Success;

			if (this.CurrentMission.Owner == this.Game.Player1)
				this.CurrentMission.Owner.UserInput.SelectSingleCardInput(new Input.Request.CallbackDelegate(this.AssignGlyph), "Select an assigned character to gain the glyph.", this.AssignedHeroCards);
			else
				this.Game.Player1.UserInput.DisplayMessage("Please wait while the opponent assigns the mission glyph.", this.AssignedHeroCards);
		}
		#endregion

		#region On Mission Failed
		/// <summary>
		/// Fire the mission failed event.
		/// </summary>
		/// <param name="mission">The mission that was failed.</param>
		protected void OnMissionFailed(CardLibrary.CardTypes.Mission mission)
		{
			this.Status = MissionStatus.Failure;
			this.FailedMissions.Add(mission);

			// stop the assigned characters
			this.StopAssignedCards();
			this.DiscardObstacles();

			// raise the event
			if (this.MissionFailed != null)
				this.MissionFailed(mission);

			// complete the mission phase
			this.Game.GameTurn.CompleteMissionPhase(false);
		}
		#endregion

		#region Assign Glyph
		/// <summary>
		/// Assign a glyph to the selected car.
		/// </summary>
		/// <param name="reponse">The response details.</param>
		public void AssignGlyph(Input.Response reponse)
		{
			CardTypes.Character selectedCard = reponse.SelectedCards[0] as CardTypes.Character;
			if (selectedCard != null)
			{
				Glyphs.EarnedGlyph glyph = new Wormhole.Engine.Glyphs.EarnedGlyph();
				glyph.Experience = this.CurrentMission.Experience;
				glyph.Skill = this.CurrentMission.Type;
				glyph.Glyph = this.CurrentMission.Glyph;

				selectedCard.EarnedGlyphs.Add(glyph);

				// raise the event that the glyp was assigned
				this.OnGlyphAssigned(selectedCard);

				// raise the event
				if (this.MissionSuccess != null)
					this.MissionSuccess(this.CurrentMission);

				// stop the assigned characters
				this.StopAssignedCards();
				this.DiscardObstacles();


				// complete the mission phase
				this.Game.GameTurn.CompleteMissionPhase(true);
			}
			else
			{
				throw new ApplicationException("Could not assign glyph!");
			}
		}
		#endregion

		#region On Glyph Assigned
		/// <summary>
		/// Raise the glyph assigned event.
		/// </summary>
		/// <param name="card">The card the glyp was assigned to.</param>
		protected void OnGlyphAssigned(CardTypes.Character card)
		{
			if (this.GlyphAssigned != null)
				this.GlyphAssigned(card);
		}
		#endregion

		#region Assign Card
		/// <summary>
		/// Calculate the new mission total after the card is assigned.
		/// </summary>
		/// <param name="card">The card that was assigned to the mission.</param>
		protected void AssignCard(BaseCardTypes.SkillsCard card)
		{
			this.AssignedHeroCards.Add(card);
		}
		#endregion

		#region Unassign Card
		/// <summary>
		/// Calculate the new mission total after the card is unassigned.
		/// </summary>
		/// <param name="card">The card that was unassigned to the mission.</param>
		protected void UnassignCard(BaseCardTypes.SkillsCard card)
		{
			this.AssignedHeroCards.Remove(card);
		}
		#endregion

		#region Update Total
		/// <summary>
		/// Re-calculate the mission total.
		/// </summary>
		protected void UpdateTotal()
		{
			if (this.CurrentMission != null)
			{
				if (this.AssignedHeroCards.Skills[this.CurrentMission.Type] != null)
					this.Total = this.AssignedHeroCards.Skills[this.CurrentMission.Type].Value;
				else
					this.Total = 0;
			}
		}
		#endregion

		#region Resolve
		/// <summary>
		/// Resolve the current mission.
		/// </summary>
		public void Resolve()
		{
			if (this.IsSuccess)
				this.OnMissionSuccess(this.CurrentMission);
			else
				this.OnMissionFailed(this.CurrentMission);
		}
		#endregion

		#region Stop Assigned Cards
		/// <summary>
		/// Stop cards that were assigned to the current mission.
		/// </summary>
		private void StopAssignedCards()
		{
			List<CardLibrary.BaseCardTypes.SkillsCard> cards = new List<Wormhole.Engine.CardLibrary.BaseCardTypes.SkillsCard>();

			// get the list of assigned cards
			foreach (CardLibrary.BaseCardTypes.SkillsCard card in this.AssignedHeroCards)
			{
				cards.Add(card);
			}

			// stop and unassign the cards
			foreach (CardLibrary.BaseCardTypes.SkillsCard card in cards)
			{
				card.Unassign();
				card.Stop();
			}
		}
		#endregion

		#region Discard Obstacles
		/// <summary>
		/// Discard the obstacle cards which was played.
		/// </summary>
		private void DiscardObstacles()
		{
			List<CardLibrary.BaseCardTypes.Card> cards = new List<Wormhole.Engine.CardLibrary.BaseCardTypes.Card>();

			// get the list of cards
			foreach (CardLibrary.BaseCardTypes.Card card in this.FaceUpObstacles)
			{
				cards.Add(card);
			}

			// discard the cards
			foreach (CardLibrary.BaseCardTypes.Card card in cards)
			{
				this.FaceUpObstacles.Remove(card);
				card.Owner.Graveyard.Add(card);
			}
		}
		#endregion

		#region Replace Failed Missions
		/// <summary>
		/// Ask the user to place failed missions at the bottom of the mission pile.
		/// </summary>
		public void ReplaceFailedMissions()
		{
			if (this.CurrentMission.Owner == this.Game.Player1)
				this.CurrentMission.Owner.UserInput.SelectSingleCardInput(new Input.Request.CallbackDelegate(this.ReplaceMission), "Select a failed mission to place at the bottom of the mission pile.", this.FailedMissions);
			else
				this.Game.Player1.UserInput.DisplayMessage("Please wait for the opponent to place missions at the bottom of his mission pile", this.FailedMissions);
		}
		#endregion

		#region Replace Mission
		/// <summary>
		/// Place a mission card at the bottom of the missions piles.
		/// </summary>
		/// <param name="reponse">The user input response.</param>
		public void ReplaceMission(Input.Response reponse)
		{
			// get the mission card to replace
			BaseCardTypes.Card mission = reponse.SelectedCards[0];
			
			// remove the card from the failed missions deck
			this.FailedMissions.Remove(mission);

			// add the card to the missions pile
			mission.Owner.Missions.Add(mission);

			// complete this phase if the last card was removed
			if (this.FailedMissions.Count == 0)
				this.Game.GameTurn.CompleteDebriefPhase();

			// raise the event that the mission was replaced
			this.OnReplaceMissionBottom(mission);
		}
		#endregion

		#region On Replace Mission
		/// <summary>
		/// Fire the replace mission event to indicate that a mission was returned to the bottom of the mission pile.
		/// </summary>
		/// <param name="card">The mission that was replaced.</param>
		protected void OnReplaceMissionBottom(CardLibrary.BaseCardTypes.Card card)
		{
			if (this.ReplaceMissionBottom != null)
				this.ReplaceMissionBottom(card);
		}
		#endregion

		#region On Face Up Obstacle Played
		/// <summary>
		/// Raise the face up obstacle played event.
		/// </summary>
		/// <param name="obstacle">The obstacle which was played.</param>
		protected virtual void OnFaceUpObstaclePlayed(CardTypes.Obstacle obstacle)
		{
			this.FaceUpObstacles.Add(obstacle);
			this.UpdateDifficulty();

			if (this.FaceUpObstaclePlayed != null)
				this.FaceUpObstaclePlayed(obstacle);
		}
		#endregion

		#region Update Difficulty
		/// <summary>
		/// Update the mission difficulty
		/// </summary>
		protected void UpdateDifficulty()
		{
			this.Difficulty = this.CurrentMission.Difficulty;

			if (this.FaceUpObstacles.Skills[this.CurrentMission.Type] != null)
				this.Difficulty += this.FaceUpObstacles.Skills[this.CurrentMission.Type].Value;

			this.OnDifficultyChanged(this.Difficulty);
		}
		#endregion

		#region On Difficulty Changed
		/// <summary>
		/// Fire the difficulty changed event.
		/// </summary>
		/// <param name="difficulty">The new difficulty.</param>
		protected void OnDifficultyChanged(int difficulty)
		{
			if (this.DifficultyChanged != null)
				this.DifficultyChanged(difficulty);
		}
		#endregion
	}
}
